/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.minidota.game;

import com.minidota.arrays.ConcurrentObjectsList;
import com.minidota.arrays.UsersList;
import com.minidota.common.Map;
import com.minidota.jetty.GameWebSocket;
import com.minidota.model.Arrow;
import com.minidota.model.BaseObject;
import com.minidota.model.Creep;
import com.minidota.model.Player;
import com.minidota.model.Tower;
import com.minidota.model.User;
import com.minidota.model.Vector2;
import com.minidota.utils.Util;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 *
 * @author custom
 */
public class Room implements Runnable {

    private int id;
    private UsersList users;
    private ConcurrentObjectsList arrows = new ConcurrentObjectsList();
    private CopyOnWriteArrayList<Tower> towers = new CopyOnWriteArrayList<Tower>();
    public static CopyOnWriteArrayList<Room> roomList = new CopyOnWriteArrayList<Room>();
    public static final int TICK_DELAY = 200;
    private long tick = 0;
    private Map map;
    private int arrowsIdCounter = 0;
    private int gameIdCounter = 0;
    private long tickToEndRound = 0;
    private long lastWaveTick;// тик в котором была последняя волна крипов
    private static final int betweanWaveTick = 150;// тиков между волнами

    public Room() {
    }

    public static Room getRoomById(int id) {
        for (Room room : roomList) {
            if (room.getId() == id) {
                return room;
            }
        }
        return null;
    }

    /**
     * обновляет данные всех объектов
     *
     * @param tick
     */
    public void updateData(long tick) {
        if (getLastWaveTick() + getBetweanWaveTick() <= tick) {
            nextWaveCreeps();
            setLastWaveTick(tick);
        }
        if (getTickToEndRound() != 1) {//все впорядке
            for (BaseObject a : getArrows()) {
                a.updateData(tick);
            }
            for (BaseObject u : getUsers()) {
                u.updateData(tick);
            }
            for (BaseObject t : getTowers()) {
                t.updateData(tick);
            }
        } else {//раунд закончен
            System.out.println("Раунд закончился");
            restore(); //удаляем массивы
        }
        if (getTickToEndRound() > 0) {
            setTickToEndRound(getTickToEndRound() - 1);
        }
    }

    private void nextWaveCreeps() {
        for (int counterLine = 1; counterLine <= 3; counterLine++) {
            for (int counterCreep = 0; counterCreep < 3; counterCreep++) {
                Creep creepDark = new Creep(this, 2, counterLine);
                if (this.addUser(creepDark)) {
                    creepDark.relive();
                }
                Creep creepLight = new Creep(this, 1, counterLine);
                if (this.addUser(creepLight)) {
                    creepLight.relive();
                }
            }
        }
    }

    /**
     * возвращает информацию обо всех игроках, предназначенную данному сокету
     *
     * @param ws
     * @return
     */
    public StringBuilder getUsersSendData(User u) {
        StringBuilder screen = new StringBuilder();
        //me
        screen.append(Util.getCharByCode(u.getGameId()));
        screen.append(Util.getMsgSeparator());
        //users
        for (BaseObject bo : getUsers()) {
            if (bo instanceof User) {
                User user = (User) bo;
                if (user.isAlive()) {
                    screen.append(user.getSendData());
                }
            }
        }
        screen.append(Util.getMsgSeparator());
        //selectedUser
        if (u instanceof User) {
            if (u.getSelected() instanceof User) {
                screen.append(Util.getCharByCode(0)).append(((User) u.getSelected()).getExtendedData());
            } else if (u.getSelected() instanceof Tower) {
                screen.append(Util.getCharByCode(1)).append(((Tower) u.getSelected()).getExtendedData());
            }
        }
        return screen;
    }

    /**
     * возвращает информацию обо всех стрелах, предназначенную данному сокету
     *
     * @param ws
     * @return
     */
    public StringBuilder getArrowsSendData(User u) {
        StringBuilder screen = new StringBuilder();
        //arrows
        for (BaseObject bo : getArrows()) {
            if (bo instanceof Arrow) {
                Arrow a = (Arrow) bo;
                screen.append(a.getSendData());
            }
        }
        return screen;
    }

    /**
     * возвращает информацию обо всех башнях, предназначенную данному сокету
     *
     * @param ws
     * @return
     */
    public StringBuilder getTowersSendData(User u) {
        StringBuilder screen = new StringBuilder();
        //arrows
        for (BaseObject bo : getTowers()) {
            if (bo instanceof Tower) {
                Tower t = (Tower) bo;
                if (t.isAlive()) {
                    screen.append(t.getSendData());
                }
            }
        }
        return screen;
    }

    /**
     * возвращает информацию обо всех объектах, предназначенную данному сокету
     *
     * @param ws
     * @return
     */
    public String getSendData(User u) {
        StringBuilder sb = new StringBuilder();
        //users
        sb.append(getUsersSendData(u));
        sb.append(Util.getMsgSeparator());
        sb.append(getArrowsSendData(u));
        sb.append(Util.getMsgSeparator());
        sb.append(getTowersSendData(u));
        return sb.toString();
    }

    public boolean isUserPresent(int userId) {
        boolean res = false;
        for (User u : getUsers()) {
            if (u.getId() == userId) {
                res = true;
                break;
            }
        }
        return res;
    }

    /**
     * добавляет user в массив игроков
     *
     * @param user
     * @return
     */
    public boolean addUser(User user) {
        boolean res = false;
        res = getUsers().add(user);
        if (res) {
            sendInitInfo(user);
            sendAddInfo(user);
        }
        return res;
    }

    /**
     * отправляет информацию всем сокетам о заходе в игру нового игрока
     *
     * @param user
     * @param excepted
     */
    public void sendAddInfo(User user) {
        //отправка набора всем
        ArrayList<Object> au = new ArrayList<Object>();
        au.add(user.getJsonData());
        String addUnitInfo = "au" + GameWebSocket.json.toJson(au);
        for (User u : users) {
            if (u.getSocket() != null) {
                u.getSocket().sendMsg(addUnitInfo);
            }
        }
    }

    /**
     * отправляет информацию обо всех игроках подключившемуся к игре сокету
     *
     * @param ws
     */
    public void sendInitInfo(User u) {
        StringBuilder sb = new StringBuilder();
        sb.append("in");
        if (u.getSocket() != null) {
            u.getSocket().sendMsg(sb.toString());
        }
        //отправка всего набора user ws
        ArrayList<Object> au = new ArrayList<Object>();
        for (User user : users) {
            au.add(user.getJsonData());
        }
        if (u.getSocket() != null) {
            u.getSocket().sendMsg("au", GameWebSocket.json.toJson(au));
        }
    }

    /**
     * отправляет информацию всем сокетам о выходе из игры одного из игроков
     *
     * @param user
     */
    private void sendDeleteUnitInfo(User user) {
        //отправка набора всем
        StringBuilder sb = new StringBuilder();
        sb.append("dl");
        sb.append(Util.getCharByCode(user.getId()));
        for (User u : users) {
            if (u.getId() != user.getId()) {
                if (u.getSocket() != null) {
                    u.getSocket().sendMsg(sb.toString());
                }
            }
        }
    }

    /**
     * удаление user из учатсников комнаты
     *
     * @param user
     * @return
     */
    public boolean disconectPlayer(User user) {
        if (user instanceof Player && user.getSocket() != null) {
            user.getSocket().disconnect();
            user.setDisconected(true);
        }
        return true;
    }

    /**
     * удаление user из учатсников комнаты
     *
     * @param user
     * @return
     */
    public boolean removeUser(User user) {
        boolean res = getUsers().remove(user);
        if (res) {
            //отправляем всем что я удалился 
            sendDeleteUnitInfo(user);
            user.setRoom(null);
            //user.setHP(0);
            if (user instanceof Player && user.getSocket() != null) {
                user.getSocket().setRoom(null);
            }
            for (User u : users) {
                if (u.getTarget() != null && u.getTarget().getGameId() == user.getGameId()) {
                    u.setTarget(null);
                }
            }
        }
        return res;
    }

    /**
     * создаем комнату с номером id
     *
     * @param id - номер комнаты
     * @return
     */
    public static synchronized Room addNewRoom(Room newRoom) {
        for (Room temp : roomList) {
            if (temp.getId() == newRoom.getId()) {
                return temp;
            }
        }
        if (newRoom.start()) {
            roomList.add(newRoom);
            return newRoom;
        } else {
            return null;
        }
    }

    public void restore() {
        this.arrows = new ConcurrentObjectsList();
        if (getUsers() != null) {
            for (User u : getUsers()) {
                this.removeUser(u);
            }
        }
        this.setUsers(new UsersList(this));
        this.towers = new CopyOnWriteArrayList<Tower>();

        ArrayList<Tower> prevTowers = new ArrayList<Tower>();
        //Темные
        //Mid
        Tower t3 = new Tower(this, 2, new Vector2(2730, 1750), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        Tower tMid = new Tower(this, 2, new Vector2(3400, 1100), 1, 140, prevTowers);
        tMid.init();

        //top
        t3 = new Tower(this, 2, new Vector2(1400, 400), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        Tower tTop = new Tower(this, 2, new Vector2(3000, 400), 1, 140, prevTowers);
        tTop.init();

        //bottom
        t3 = new Tower(this, 2, new Vector2(4150, 3000), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        Tower tBottom = new Tower(this, 2, new Vector2(4150, 1400), 1, 140, prevTowers);
        tBottom.init();
        //main
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(tMid);
        prevTowers.add(tTop);
        prevTowers.add(tBottom);
        Tower tMain = new Tower(this, 2, new Vector2(4050, 450), 2, 180, prevTowers);//Вышка последня темных
        tMain.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(tMain);
        new Tower(this, 2, new Vector2(4200, 300), 0, 0, prevTowers).init();//MainBUILD

        //Светлые
        //Mid
        t3 = new Tower(this, 1, new Vector2(1750, 2730), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        tMid = new Tower(this, 1, new Vector2(1100, 3400), 1, 140, prevTowers);
        tMid.init();

        //top
        t3 = new Tower(this, 1, new Vector2(400, 1300), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        tTop = new Tower(this, 1, new Vector2(400, 3100), 1, 140, prevTowers);
        tTop.init();
        //bottom
        t3 = new Tower(this, 1, new Vector2(3100, 4150), 1, 100, null);
        t3.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(t3);
        tBottom = new Tower(this, 1, new Vector2(1500, 4150), 1, 140, prevTowers);
        tBottom.init();
        //Main 
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(tMid);
        prevTowers.add(tTop);
        prevTowers.add(tBottom);
        tMain = new Tower(this, 1, new Vector2(450, 4050), 2, 180, prevTowers);//Вышка последня светлых
        tMain.init();
        prevTowers = new ArrayList<Tower>();
        prevTowers.add(tMain);
        new Tower(this, 1, new Vector2(300, 4200), 0, 0, prevTowers).init();//base
    }

    /**
     * стартуем комнату
     */
    public boolean start() {
        this.setMap(Map.getMapById(2));
        if (getMap() != null) {
            restore();
            Thread t = new Thread(this);
            t.start();
            return true;
        }
        return false;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        try {
            while (true) {
                tick++;
                updateData(getTick());
                long startTime = System.currentTimeMillis();
                for (User u : getUsers()) {
                    if (u.getSocket() != null) {
                        u.getSocket().sendMsg("un", getSendData(u));
                    }
                }
                long tickTime = System.currentTimeMillis() - startTime;
                if (tickTime > 100) {
                    System.out.println(Util.DATE_FORMAT.format(new Date()) + " server#" + getId() + " tickTime=" + tickTime);
                }
                try {
                    if ((TICK_DELAY - tickTime) >= 0) {
                        Thread.sleep(TICK_DELAY - tickTime);
                    }
                } catch (InterruptedException ex) {
                    System.out.println(ex.getMessage());
                }
            }
        } catch (Exception ex) {
            System.out.println("room_id" + getId() + " " + ex.toString());
            ex.printStackTrace();
        }

    }

    /**
     * @return the map
     */
    public Map getMap() {
        return map;
    }

    /**
     * @param map the map to set
     */
    public void setMap(Map map) {
        this.map = map;
    }

    /**
     * @return the users
     */
    public UsersList getUsers() {
        return users;
    }

    /**
     * @param users the users to set
     */
    public void setUsers(UsersList users) {
        this.users = users;
    }

    /**
     * @return the tick
     */
    public long getTick() {
        return tick;
    }

    /**
     * @return the arrows
     */
    public ConcurrentObjectsList getArrows() {
        return arrows;
    }

    /**
     * @param arrows the arrows to set
     */
    public void setArrows(ConcurrentObjectsList arrows) {
        this.arrows = arrows;
    }

    public int getFreeRocketId() {
        if (arrowsIdCounter >= 0x6C00) {
            arrowsIdCounter = 0;
        }
        return arrowsIdCounter++;
    }

    public int getFreeGameId() {
        int res = -1;
        while (res < 0) {
            res = getNextValue();
        }
        return res;
    }

    private int getNextValue() {
        if (gameIdCounter >= 50000) {
            gameIdCounter = 0;
        }
        int nextValue = gameIdCounter++;
        for (User user : this.getUsers()) {
            if (nextValue == user.getGameId()) {
                return -1;
            }
        }
        for (Tower t : this.getTowers()) {
            if (nextValue == t.getGameId()) {
                return -1;
            }
        }
        return nextValue;
    }

    /**
     * @return the towers
     */
    public CopyOnWriteArrayList<Tower> getTowers() {
        return towers;
    }

    /**
     * @param towers the towers to set
     */
    public void setTowers(CopyOnWriteArrayList<Tower> towers) {
        this.towers = towers;
    }

    /**
     * @return the tickEndRound
     */
    public long getTickToEndRound() {
        return tickToEndRound;
    }

    /**
     * @param tickEndRound the tickEndRound to set
     */
    public void setTickToEndRound(long tickToEndRound) {
        this.tickToEndRound = tickToEndRound;
    }

    /**
     * @return the lastWaveTick
     */
    public long getLastWaveTick() {
        return lastWaveTick;
    }

    /**
     * @param lastWaveTick the lastWaveTick to set
     */
    public void setLastWaveTick(long lastWaveTick) {
        this.lastWaveTick = lastWaveTick;
    }

    /**
     * @return the betweanWaveTick
     */
    public static int getBetweanWaveTick() {
        return betweanWaveTick;
    }

}
